﻿using System;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using GIC.BO.Access;
using GIC.DAO.Model;
using System.IO;
using System.Threading;
using System.Runtime.CompilerServices;
using System.Collections;

namespace GIC.UI
{
    public class SocketThread
    {
        private String ip;
        private int porta;
        private Object lockThis = new Object();
        GICBo bo = new GICBo();
        Parametros parametros = new Parametros();

        StreamWriter logfile;
        private Socket socket;
        private Socket handler;
        private static ManualResetEvent allDone = new ManualResetEvent(false);
        public AsyncCallback pfnWorkerCallBack;
        string appdata_folder = Application.StartupPath.ToString() + "\\Log";
        
        public SocketThread(String ip, int porta)
        {
            // TODO: Complete member initialization
            this.ip = ip;
            this.porta = porta;

            parametros = bo.obterParametro();
            apagarArquivos(appdata_folder);
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private void gravaLog(string msg)
        {
            if (msg != null && msg != String.Empty)
            {
                string ts = DateTime.Now.ToString("yyyy_MM_dd_HH");

                if (!Directory.Exists(appdata_folder))
                {
                    Directory.CreateDirectory(appdata_folder);
                }

                logfile = File.AppendText(String.Format(@"{0}\LOG_SOCKET_{1}.log", appdata_folder, ts));
                logfile.Write(msg);

                logfile.Flush();
                logfile.Close();
            }
        }


        public void iniciarSocket(IAsyncResult asyncAccept)
        {
            String msgRecebida;
            StringBuilder log = new StringBuilder();

            SocketPacket socketData = (SocketPacket)asyncAccept.AsyncState; 
 
            try
            {
                string retorno = string.Empty;
                int bytesRec;

                bytesRec = socketData.m_currentSocket.EndReceive(asyncAccept);
                char[] chars = new char[bytesRec];
                Decoder d = Encoding.Default.GetDecoder();
                int charLen = d.GetChars(socketData.dataBuffer, 0, bytesRec, chars, 0);
                msgRecebida = new String(chars);

                if (msgRecebida != null && msgRecebida != String.Empty)
                {

                    string dataHoje = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss").ToString();
                    log.AppendLine(dataHoje + "_MES_" + msgRecebida + "\n");

                    if (parametros != null && parametros.deviceID != null && parametros.deviceID != "" && (msgRecebida.Contains(parametros.deviceID.TrimEnd().TrimStart().Trim())))
                    {
                        try
                        {
                            retorno = salvarInformacoes(msgRecebida);

                            if (retorno.EndsWith("OK") || retorno.EndsWith("NG"))
                            {
                                retorno = retorno.Substring(0, 47);
                            }
                            else
                            {
                                retorno = retorno.Substring(0, 45);
                            }

                            byte[] msg = new byte[1024];
                            // Echo the data back to the client.
                            msg = Encoding.ASCII.GetBytes(retorno);

                            handler = (Socket)socketData.m_currentSocket;
                            handler.BeginSend(msg, 0, msg.Length, 0, new AsyncCallback(SendCallback), handler);

                            log.AppendLine(dataHoje + "_OEM_" +  new ASCIIEncoding().GetString(msg) + "\n");

                            WaitForData(socketData.m_currentSocket, socketData.m_clientNumber);
                        }
                        catch (SemInformacoesException e)
                        {
                            handler.Shutdown(SocketShutdown.Both);
                            handler.Close();
                        }
                    }
                    else
                    {
                        byte[] msg = Encoding.ASCII.GetBytes("DeviceID incorreto com o cadastrado!\nNenhum processamento efetuado... Socket desconectado! \n");
                        handler.Send(msg);
                        handler.Shutdown(SocketShutdown.Both);
                        handler.Close();
                    }
                }
            }
            catch (Exception e)
            {
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();
            }
            gravaLog(log.ToString());
        }

        public void WaitForData(Socket soc, int clientNumber)
        {
            try
            {
                if (pfnWorkerCallBack == null)
                {
                    // Specify the call back function which is to be 
                    // invoked when there is any write activity by the 
                    // connected client
                    pfnWorkerCallBack = new AsyncCallback(iniciarSocket);
                }
                SocketPacket theSocPkt = new SocketPacket(soc, clientNumber);

                soc.BeginReceive(theSocPkt.dataBuffer, 0,
                    theSocPkt.dataBuffer.Length,
                    SocketFlags.None,
                    pfnWorkerCallBack,
                    theSocPkt);
            }
            catch (SocketException se)
            {
                MessageBox.Show(se.Message);
            }
        }

        private static void SendCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.
                Socket client = (Socket)ar.AsyncState;

                // Complete sending the data to the remote device.
                int bytesSent = client.EndSend(ar);

                // Signal that all bytes have been sent.
                allDone.Set();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        /// <summary>
        /// Apaga os arquivos de Log dos dias anteriores
        /// </summary>
        private void apagarArquivos(String path)
        {
            DirectoryInfo diretorio = new DirectoryInfo(String.Format(@"{0}", path));

            if (diretorio != null && diretorio.Exists)
            {
                //Este for each percorre todos os arquivos do diretório
                foreach (var item in diretorio.GetFiles())
                {
                    //Pega a diferença(tempo) entre a ultima data de escrita e hoje
                    TimeSpan t = DateTime.Now.Subtract(item.LastWriteTime);
                    // Se última vez que o arq foi escrito for maior que 1 dia, deleta o arquivo
                    if (t.Days > 2)
                    {
                        if (item.ToString().Substring(item.ToString().IndexOf(".")) == ".log")
                        {
                            item.Delete();
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Ativação Socket
        /// </summary>
        /// <returns></returns>
        public void socketAtivo()
        {
            IPAddress[] ipAddress = null;
            try
            {
                ipAddress = Dns.GetHostAddresses(this.ip);
            }
            catch
            {
                MessageBox.Show("IP '" + this.ip + "' Inválido para iniciar o Socket\nO Socket não será iniciado!",
                                              "Socket",
                                              MessageBoxButtons.OK,
                                              MessageBoxIcon.Error,
                                              MessageBoxDefaultButton.Button1);
            }

            IPEndPoint localEndPoint = new IPEndPoint(ipAddress[0], this.porta);

            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            try
            {
                socket.Bind(localEndPoint);
                socket.Listen(1);

                while (true)
                {
                    // Set the event to nonsignaled state.
                    allDone.Reset();

                    // Start an asynchronous socket to listen for connections.
                    socket.BeginAccept(new AsyncCallback(connectClient), socket);

                    // Wait until a connection is made before continuing.
                    allDone.WaitOne();
                }
            }
            catch (SocketException se)
            {
                MessageBox.Show("Socket já iniciado ou IP " + ipAddress[0] + " não é o local!",
                                           "Problemas ao iniciar Socket",
                                           MessageBoxButtons.OK,
                                           MessageBoxIcon.Error,
                                           MessageBoxDefaultButton.Button1);
            }
        }

        public void connectClient(IAsyncResult asyn)
        {
            try
            {
                // Here we complete/end the BeginAccept() asynchronous call
                // by calling EndAccept() - which returns the reference to
                // a new Socket object
                Socket workerSocket = socket.EndAccept(asyn);

                // Let the worker Socket do the further processing for the 
                // just connected client
                WaitForData(workerSocket, 0);  //m_clientCount);

                // Since the main Socket is now free, it can go back and wait for
                // other clients who are attempting to connect
                socket.BeginAccept(new AsyncCallback(connectClient), null);
            }
            catch (ObjectDisposedException)
            {
                System.Diagnostics.Debugger.Log(0, "1", "\n clientConnection: Socket has been closed\n");
            }
            catch (SocketException se)
            {
                MessageBox.Show(se.Message);
            }
        }

        public String salvarInformacoes(String msgRecebida)
        {
            lock (lockThis)
            {
                //primeiro passo
                if (msgRecebida.Equals(Enum.GetName(typeof(FlagRequisicao), FlagRequisicao.REQ) + parametros.deviceID))
                {
                    return processarSegundoPasso();
                }
                else
                {
                    SocketHyundai socket = null;

                    //terceiro passo
                    foreach (FlagRequisicao flag in Enum.GetValues(typeof(FlagRequisicao)))
                    {
                        if (msgRecebida.Contains(Enum.GetName(typeof(FlagRequisicao), flag)) && (flag == FlagRequisicao.SND || flag == FlagRequisicao.PUD))
                        {
                            socket = processarTerceiroPasso(msgRecebida);
                            break;
                        }
                    }

                    if (socket != null)
                    {
                        StatusBodyNo statusRetorno = StatusBodyNo.OK;
                        try
                        {
                            BodyNoReenvio[] socketReenvio = bo.atualizarSocketReenvio(socket.BODY_NO);

                            if (socketReenvio.Length > 0)
                            {
                                foreach (BodyNoReenvio sr in socketReenvio)
                                {
                                    sr.Status = Enum.GetName(typeof(StatusBodyNoReenvio), StatusBodyNoReenvio.Confirmado);
                                    bo.salvar(sr);
                                    bo.deletarSocketReenvioPendente(sr.BodyNo);
                                }
                            }

                            bo.salvar(socket);
                            this.salvarChassi(socket.chassi, socket.SEQ_DIA);
                        }
                        catch
                        {
                            statusRetorno = StatusBodyNo.NG;
                        }

                        return gerarRetornoQuartoPasso(socket, statusRetorno);                    
                    }
                }
            }
            throw new SemInformacoesException("Sem informações para processamento!");
        }

        private string gerarRetornoQuartoPasso(SocketHyundai socket, StatusBodyNo statusRetorno)
        {
            String retorno = socket.FlagRequisicao + parametros.deviceID;
            retorno = String.Concat(retorno, socket.SN); //Data Type
            retorno = String.Concat(retorno, socket.SpoolPoint); //Spool Point
            retorno = String.Concat(retorno, socket.dataChassiSocket.Year.ToString()); //Ano Prod Date
            retorno = String.Concat(retorno, socket.dataChassiSocket.ToString("MM")); //Mês Prod Date
            retorno = String.Concat(retorno, socket.dataChassiSocket.ToString("dd")); //Dia Prod Date
            retorno = String.Concat(retorno, socket.StationCode); //Station Code
            retorno = String.Concat(retorno, socket.SEQ_DIA); //SEQ
            retorno = String.Concat(retorno, socket.BODY_NO); //Body No.
            retorno = String.Concat(retorno, Enum.GetName(typeof(StatusBodyNo), statusRetorno)); //Status de retorno

            retorno.TrimStart();
            retorno.TrimEnd();
            return retorno;
        }

        private string processarSegundoPasso()
        {
            String retorno;

            BodyNoReenvio lastSocketReenvio = bo.recuperarSocketPendente();

            //Verifica se existe BodyNo de Reenvio Pendente
            if (lastSocketReenvio != null && (lastSocketReenvio.Status == Enum.GetName(typeof(StatusBodyNoReenvio), StatusBodyNoReenvio.Pendente)))
            {
                retorno = Enum.GetName(typeof(FlagRequisicao), FlagRequisicao.RSD) + parametros.deviceID;
                retorno = retorno.PadRight(retorno.Length + 2, '0'); //Data Type
                retorno = retorno.PadRight(retorno.Length + 5, '0'); //Spool Point
                retorno = retorno.PadRight(retorno.Length + 8, '0'); //Prod Date
                retorno = retorno.PadRight(retorno.Length + 4, '0'); //Station Code
                retorno = retorno.PadRight(retorno.Length + 4, '0'); //SEQ
                retorno = String.Concat(retorno, lastSocketReenvio.BodyNo); //Body No.
                retorno = retorno.PadRight(retorno.Length + 2, ' ');
            }
            else
            {
                SocketHyundai lastSocket = bo.recuperarUltimoSocket();

                //Verifica se existe registro de último BodyNo cadastrado na tabela principal
                if (lastSocket != null)
                {
                    retorno = Enum.GetName(typeof(FlagRequisicao), FlagRequisicao.REQ) + parametros.deviceID; 
                    retorno = String.Concat(retorno, lastSocket.SN); //Data Type
                    retorno = String.Concat(retorno, lastSocket.SpoolPoint); //Spool Point
                    retorno = String.Concat(retorno, lastSocket.dataChassiSocket.ToString("yyyyMMdd")); //Prod Date
                    retorno = String.Concat(retorno, lastSocket.StationCode); //Station Code
                    retorno = String.Concat(retorno, lastSocket.SEQ_DIA); //SEQ
                    retorno = String.Concat(retorno, lastSocket.BODY_NO); //Body No.
                }
                else
                {
                    retorno = Enum.GetName(typeof(FlagRequisicao), FlagRequisicao.RSD) + parametros.deviceID;
                    retorno = retorno.PadRight(retorno.Length + 2, '0'); //Data Type
                    retorno = retorno.PadRight(retorno.Length + 5, '0'); //Spool Point
                    retorno = retorno.PadRight(retorno.Length + 8, '0'); //Prod Date
                    retorno = retorno.PadRight(retorno.Length + 4, '0'); //Station Code
                    retorno = retorno.PadRight(retorno.Length + 4, '0'); //SEQ
                    retorno = String.Concat(retorno, Enum.GetName(typeof(DefaultBodyNo), DefaultBodyNo.IJA_000000)); //Body No.
                    retorno = retorno.PadRight(retorno.Length + 2, ' ');
                }
            }

            return retorno;
        }

        private SocketHyundai processarTerceiroPasso(String msgRecebida)
        {
            if (msgRecebida.Length < 52)
            {
                throw new Exception("A mensagem recebida não está no formato esperado: \"" +msgRecebida+ "\"");
            }

            String flag;
            String DataType;
            String spoolPoint;
            String dataChassiSocket;
            String stationCode;
            String seqDia;
            String bodyNO;
            String orderData;

            flag = msgRecebida.Substring(0, 3); //Tipo de Requisição
            DataType = msgRecebida.Substring(10, 2); //Data Type
            spoolPoint = msgRecebida.Substring(12, 5); //Spool Point
            dataChassiSocket = msgRecebida.Substring(17, 8); //Prod Date
            stationCode = msgRecebida.Substring(25, 4); //Station Code
            seqDia = msgRecebida.Substring(29, 4); //SEQ
            bodyNO = msgRecebida.Substring(33, 12); //Body No.
            orderData = msgRecebida.Substring(45, 8); //VIN Number

            SocketHyundai socketHyundai = new SocketHyundai();

            socketHyundai.FlagRequisicao = flag; 
            socketHyundai.SN = DataType;
            socketHyundai.SpoolPoint = spoolPoint;
            socketHyundai.dataOcorrencia = DateTime.Now;
            socketHyundai.StationCode = stationCode;
            socketHyundai.SEQ_DIA = seqDia;
            socketHyundai.BODY_NO = bodyNO;
            socketHyundai.chassi = orderData;
                
            String sDataInicio = dataChassiSocket.Substring(6, 2).ToString() + "/" + dataChassiSocket.Substring(4, 2).ToString() + "/" + dataChassiSocket.Substring(0, 4).ToString();
            DateTime dtSocket = Convert.ToDateTime(sDataInicio).AddHours(0).AddMinutes(0).AddSeconds(0).AddMilliseconds(0);
            socketHyundai.dataChassiSocket = dtSocket;

            return socketHyundai;
        }

        private void salvarChassi(string bodyNo, string SEQ)
        {
            Chassi chassi = new Chassi();

            chassi.bodyNumber = bodyNo;
            chassi.dataOcorrencia = DateTime.Now;
            chassi.impresso = 0;
            chassi.reportDate = DateTime.Now;
            chassi.SEQ = long.Parse(SEQ);

            bo.salvar(chassi);         
        }
         
        class SemInformacoesException : Exception
        {
            public SemInformacoesException()
            {
            }

            public SemInformacoesException(String msg) : base (msg){}
        }
    }

    public class StateObject
    {
        // Client socket.
        public Socket workSocket = null;
        // Size of receive buffer.
        public const int BufferSize = 256;
        // Receive buffer.
        public byte[] buffer = new byte[BufferSize];
        // Received data string.
        public StringBuilder sb = new StringBuilder();
    }

    public class SocketPacket
    {
        // Constructor which takes a Socket and a client number
        public SocketPacket(Socket socket, int clientNumber)
        {
            m_currentSocket = socket;
            m_clientNumber = clientNumber;
        }
        public Socket m_currentSocket;
        public int m_clientNumber;
        // Buffer to store the data sent by the client
        public byte[] dataBuffer = new byte[1024];
    }
}
